import time
import traceback
from functools import partial
from celery import shared_task
from typing import List, Callable
from requests.sessions import Session
from pydantic import TypeAdapter, parse_obj_as
from core.step_model import StepModel
from apps.envs.models import Env, Headers, EnvService
from apps.interfaces.models import Api
from apps.tests.models import Case, Step, CaseRunLog
from apps.scripts.models import PythonScript
from core.common import formatter_log, loop_for_iterator, set_user_params, get_env_params_by_env_id
from core.faker import FAKER_FUNC_MAP
from core.run_step_api import ApiModel
from core.run_step_api import run_step_api
from core.run_step_func import run_step_inner_func, run_step_script
from core.run_step_web_ui import run_step_web_ui
from core.condition_control import is_run_step, get_loop_number


class StepType:
    FUNCTION = 0
    API = 1
    SCRIPT = 2
    UI = 3
    STEP = 4


step_type_function = {
    StepType.FUNCTION: run_step_inner_func,
    StepType.API: run_step_api,
    StepType.SCRIPT: run_step_script,
    StepType.UI: run_step_web_ui,
}


class SessionDriverManage:

    def __init__(self):
        self.session_manager = {}
        self.driver_manager = {}

    def add_session_manager(self, host):
        if host not in self.session_manager:
            self.session_manager[host] = Session()

    def close_all(self):
        [session.close() for session in self.session_manager.values()]
        [selenium_action.driver.quit() for selenium_action in self.driver_manager.values()]


def __run(manager_obj, steps: List[StepModel], env_id, params_class, case_logs_obj,
          one_step_data: StepModel, step_key,
          step_type, step_index):
    step_run_func: Callable = partial(step_type_function.get(step_type), manager_obj, steps, step_key, env_id,
                                      one_step_data, params_class, case_logs_obj)
    # IF表达式返回True才执行步骤
    if is_run_step(one_step_data.run, params_class, case_logs_obj):
        case_logs_obj.logs.append(
            formatter_log('info', '【---------------- 步骤{}开始执行 --------------】'.format(step_index + 1)))
        # 循环执行
        is_for_run = get_loop_number(one_step_data, params_class, case_logs_obj)
        if is_for_run:
            [step_run_func() for _ in range(is_for_run)]
        # while 执行
        elif one_step_data.until:
            while True:
                if not one_step_data.step_after:
                    if is_run_step(one_step_data.until, params_class, case_logs_obj):
                        case_logs_obj.logs.append(formatter_log('info', '【退出While循环】'))
                        break
                step_run_func()
                if one_step_data.step_after:
                    if is_run_step(one_step_data.until, params_class, case_logs_obj):
                        case_logs_obj.logs.append(formatter_log('info', '【退出While循环】'))
                        break
        else:
            step_run_func()


def run_one_step(manager_obj, steps: List[StepModel], env_id, params_class, case_logs_obj,
                 one_step_data: StepModel):
    """
    执行一个测试步骤
    """
    # 获取步骤类型
    step_type, step_key = one_step_data.key[0], one_step_data.key[-1]
    step_index = steps.index(one_step_data)
    if step_type == StepType.STEP:
        step_obj = Step.objects.all().get(id=step_key)
        for sub_index, sub_step in enumerate(TypeAdapter(List[StepModel]).validate_python(step_obj.steps)):
            if sub_index == 0:
                steps[step_index], one_step_data = sub_step, sub_step
                step_type, step_key = one_step_data.key[0], one_step_data.key[-1]
                __run(manager_obj, steps, env_id, params_class, case_logs_obj, one_step_data, step_key,
                      step_type, step_index)
            else:
                steps.insert(step_index + sub_index, sub_step)
    else:
        __run(manager_obj, steps, env_id, params_class, case_logs_obj, one_step_data, step_key,
              step_type, step_index)
    return one_step_data


# @shared_task
def run_one_case(env_id: int, case_id: int, user_id: int, report_id=0):
    """
    执行一条测试用例
    """
    manager_obj = SessionDriverManage()  # 管理不同微服务的接口Session和浏览器Driver对象

    # 用例执行情况和日志记录
    result = CaseRunLog.CaseResult.SUCCESS
    report_id = report_id if report_id else None
    run_case_logs = [formatter_log('info', '【用例执行环境：{}】'.format(Env.objects.all().get(id=env_id).name))]
    case_logs_obj = CaseRunLog(case_id=case_id, logs=run_case_logs, result=result, time=0,
                               create_by_id=user_id, update_by_id=user_id, report=report_id)

    # 根据用例ID+时间戳生成一个唯一的变量接受类
    params_class = type(str(case_id) + str(time.time()), (object, ), {})

    # 为用例变量添加全局变量
    for env_params_obj in get_env_params_by_env_id(env_id):
        setattr(params_class, env_params_obj.name, env_params_obj.value)

    # 根据用例ID获取用例步骤
    case = Case.objects.all().get(id=case_id)
    run_start_time = time.time()

    # 设置用例全局变量
    set_user_params(params_class, case_logs_obj, case.params, FAKER_FUNC_MAP)
    steps = TypeAdapter(List[StepModel]).validate_python(case.setup + case.steps + case.teardown)
    try:
        loop_for_iterator(map(partial(run_one_step, manager_obj, steps, env_id, params_class, case_logs_obj), steps))
    except Exception as e:
        _e = traceback.format_exc()
        case_logs_obj.result = CaseRunLog.CaseResult.ERROR
        case_logs_obj.logs.append(formatter_log('error', e))
        case_logs_obj.logs += [formatter_log('error', _) for _ in _e.split('\n')]
        save_test_log(run_start_time, case_logs_obj)
        manager_obj.close_all()
        return case, case_logs_obj

    save_test_log(run_start_time, case_logs_obj)
    manager_obj.close_all()

    return case, case_logs_obj


def save_test_log(run_start_time, case_logs_obj):
    """
    保存一条测试记录
    """
    cost_time = time.time() - run_start_time
    case_logs_obj.logs.append(formatter_log('info', '【用例花费时间：{}】'.format(cost_time)))
    case_logs_obj.time = cost_time
    case_logs_obj.save()


